import os
import re
import subprocess
from SCons.Script import *

######################################################################
# SCANNERS:
# Scanners to parse .f files and .sv files for dependencies
######################################################################
f_re = re.compile(r"^\s*-f\s(\S+\.f)$", re.M | re.I)
vlog_re = re.compile(r"^\s*(/?[^/+]\S+\.s?vh?)$", re.M | re.I)
include_re = re.compile(r'^\s*`include\s+"(\S+)"$', re.M | re.I)


def flist_scan(node, env, path, arg=None):
    contents = node.get_text_contents()
    starting_dir = str(node.dir)

    vlog_files = vlog_re.findall(contents)
    vlog_files = [
        (
            os.path.join(starting_dir, vlog.strip())
            if not os.path.isabs(vlog.strip())
            else vlog.strip()
        )
        for vlog in vlog_files
    ]
    f_files = f_re.findall(contents)

    while f_files:
        f_files_ls = f_files
        for f in f_files_ls:
            # The following line is used to expand any environment variables
            # in the filepath using the custom SCons environment. This will
            # catch any variables declared in the SConstruct.
            ef = (
                subprocess.check_output("echo " + f, shell=True, env=env["ENV"])
                .decode()
                .strip()
            )
            real_path = os.path.join(starting_dir, ef)
            print(real_path)
            if os.path.isfile(real_path):
                current_dir = os.path.dirname(real_path)
                # All file paths are relative to the flist_file or absolute
                contents = env.File(real_path).get_text_contents()
                vlog_files.extend(
                    [
                        os.path.join(current_dir, x.strip())
                        for x in vlog_re.findall(contents)
                    ]
                )
                f_files.extend(
                    [
                        # turn new found file_list path to absolute path
                        os.path.abspath(os.path.join(current_dir, x.strip()))
                        for x in f_re.findall(contents)
                    ]
                )
                vlog_files.append(str(real_path))
            f_files.remove(f)
    results = []
    for f in env.File(vlog_files):
        results.extend(vincfile_scan(f, env, path, arg))
    return results


def vincfile_scan(node, env, path, arg=None):
    contents = node.get_text_contents()
    includes = include_re.findall(contents)

    starting_dir = str(node.dir)

    if includes == []:
        return [node]

    results = [str(node)]
    for inc in includes:
        f_path = os.path.join(starting_dir, inc)
        if os.path.exists(f_path):
            results.append(f_path)
    return env.File(results)


# Define the vlog scanner
VlogSuffixes = [".v", ".vh", ".sv", ".svh"]

# include file scanner
_vincscan = Scanner(
    name="vlogfile",
    function=vincfile_scan,
    skeys=[".v", ".vh", ".sv", ".svh"],
    argument=None
)

# file_list file scanner
_flistscan = Scanner(
    name="flist",
    function=flist_scan,
    skeys=[".f"],
    argument=None)

vlogscan = Scanner({'.f': _flistscan}, name='vlogscan')

for suffix in VlogSuffixes:
    vlogscan.add_scanner(suffix, _vincscan)

##################################
# Builders
##################################


def generate_vlog(source, target, env, for_signature):
    action = [env["VLOG"]]
    for s in source:
        if os.path.splitext(str(s))[1] == ".f":
            action.append("-F")
        if os.path.splitext(str(s))[1] == ".sv":
            action.append("-sv")
        action.append(str(s))
    action.extend(["-work ${SIM_DIR}${WORK}"])
    action.extend(env["VLOG_ARGS"])
    action.extend(["-l ${TARGET}"])

    return " ".join(action)


def Vlog(env, target, source, *args, **kwargs):
    """
    A pseudo builder wapper for the vlog executable.
    """
    _vlog_builder = Builder(
        generator=generate_vlog, suffix=".log",
        source_scanner=vlogscan
    )
    result = _vlog_builder(env, target, source, **kwargs)
    env.Clean(result, [".done", "${SIM_DIR}${WORK}"])
    return result


def generate_vsim(source, target, env, for_signature):
    action = [env["VSIM"]]
    action.extend([env["TOP_MOD"]])
    action.extend(["-lib ${SIM_DIR}${WORK}"])
    action.extend(env["VSIM_ARGS"])
    action.extend(["-appendlog -l $TARGET"])

    return " ".join(action)


def Vsim(env, target, source, *args, **kwargs):
    """
    A pseudo builder wrapper for the vsim executable
    """
    _vsim_builder = Builder(generator=generate_vsim, suffix=".log")
    result = _vsim_builder(env, target, source, **kwargs)
    env.Clean(result, [".done"])
    return result
